package edu.fiu.cis.selfhealingdisk;

import java.util.LinkedList;

import edu.fiu.cis.selfhealingdisk.IORequest.IOEnum;

public class MemoryEntry implements Comparable<MemoryEntry> {

	private final long pba;

	// In the final implementation the size should be constant
	private final int size;

	private long lastAccess;
	private boolean dirty = false;
	private boolean loaded= false;
	private boolean writing = false;
	
	public final LinkedList<IORequest> pendingRequest;

	public MemoryEntry(long pba, int size, long time) {
		super();
		this.pba = pba;
		this.size = size;
		this.lastAccess = time;
		this.pendingRequest = new LinkedList<IORequest>();
	}

	/**
	 * Create a write IO request using this memory entry
	 * 
	 * @param t
	 *            the time for the new IORequest
	 * @param pid
	 *            the assigned request id
	 * @return a new IO Request created with the information from this entry
	 */
	public IORequest createIORequest(long t, int pid) {
		return new IORequest(t, this.pba, this.size, IOEnum.WRITE, true);
	}

	public long getPba() {
		return pba;
	}

	public long getLastAccess() {
		return lastAccess;
	}

	public void setLastAccess(long lastAccess) {
		this.lastAccess = lastAccess;
	}

	public boolean isDirty() {
		return dirty;
	}

	public void setDirty() {
		this.dirty = true;
	}

	public void clearDirty() {
		this.dirty = false;
	}

	public int getSize() {
		return size;
	}

	/**
	 * Return the memory size for the parity block + the queue of pending events for this query
	 * @return
	 */
	public int memEntrySize() {
		return 1 + this.pendingRequest.size();
	}

	/**
	 * Add a request to the queue associated with this parity block
	 * @param ior the ior request to be enqueued
	 */
	public void addRequest(IORequest ior) {
		//The block was in memory waiting for writing. We only change to the last write
		for (IORequest pending: this.pendingRequest) {
			if (pending.getLba() == ior.getLba()) return;
		}
		this.pendingRequest.add(ior);
	}

	/**
	 * Get all the request associated with this block including the request for
	 * the parity group
	 * 
	 * @param timestamp
	 * @return
	 */
	public IORequestList getRequests(long timestamp) {
		IORequestList requests = new IORequestList();

		//This only make sense if the parity block is loaded in memory and dirty
		if (this.loaded && this.dirty) {
			int sz = this.pendingRequest.size();
			IORequest r = null;
			for (int i = 0; i < sz; i++) {
				r = this.pendingRequest.pop();
				r.setRequestTime(timestamp);
				requests.add(r);
			}
			//Add the request for the parity block
			r = new IORequest(timestamp, this.pba, this.size, IOEnum.WRITE,true);
			this.writing = true;
			requests.add(r);
		}
		return requests;
	}

	public void setLoaded() {
		this.loaded = true;
	}

	public boolean isLoaded() {
		return loaded;
	}

	/**
	 * The block can be removed from memory without problems
	 * @return true if the parity block is in memory and have not changes
	 */
	public boolean isClear() {
		return this.loaded && !this.dirty && !this.isWriting();
	}

	@Override
	public int compareTo(MemoryEntry me) {
		//Clear can be cleaned
		if (this.isClear() && !me.isClear())
			return -1;
		else if (this.isLoaded() && !me.isLoaded())
			return -1;
		else if (!this.isWriting() && me.isWriting())
			return -1;
		else {
			if (this.lastAccess == me.lastAccess)
				return 0;
			return this.lastAccess < me.lastAccess ? -1 : 1;
		}
	}
	
	public String toString() {
		return this.pba + " loaded:" + this.isLoaded() + " dirty:" + this.isDirty() + " pending:" + this.pendingRequest.size(); 
	}

	public void setWriting(boolean writing) {
		this.writing = writing;
	}

	public boolean isWriting() {
		return writing;
	}
	
	
}